package com.ocm.core.service.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;


import com.ocm.core.service.ClassInstService;
import com.ocm.core.service.OCMService;
import com.ocm.core.service.SecurityService;
import com.ocm.core.util.OCMUtil;
import com.ocm.core.mapping.DTOConfiguration;
import com.ocm.core.mapping.DTOService;
import com.ocm.core.mapping.DomainService;
import com.ocm.core.mapping.IDMapping;
import com.ocm.core.model.ClassInstance;
import com.ocm.core.model.Files;
import com.ocm.core.model.ReferenceValue;
import com.ocm.core.dao.ClassDefDao;
import com.ocm.core.dao.PropertyDefDao;
import com.ocm.core.dao.StorageDao;
import com.ocm.core.domain.AccessMask;
import com.ocm.core.domain.ClassDefinition;
import com.ocm.core.domain.ClassDefinitionType;
import com.ocm.core.domain.Permission;
import com.ocm.core.domain.PropertyDefinition;
import com.ocm.core.domain.PropertyType;
import com.ocm.core.domain.Security;
import com.ocm.core.domain.Storage;
import com.ocm.core.dto.ClassInstanceDto;
import com.ocm.core.dto.PropertyDefinitionDto;
import com.ocm.core.dto.PropertyValueDto;
import com.ocm.core.dto.SecurityDto;
import com.ocm.core.dto.StorageDto;
import com.ocm.core.exception.OCMRuntimeException;
import com.ocm.core.helper.StorageHelper;

@Service("classInstService")
@Transactional(readOnly = true)
public class ClassInstServiceImpl implements ClassInstService {

   private ClassDefDao classDefDao;

   private DTOService dtoService;

   private DomainService domainService;

   private OCMService ocmService;

   private SecurityService securityService;

   private StorageDao storageDao;

   private PropertyDefDao propertyDefDao;

   @Autowired
   private StorageHelper storageHelper;

   @Autowired
   public ClassInstServiceImpl(
         @Qualifier("storageDao")StorageDao storageDao,
         @Qualifier("classDefDao")ClassDefDao classDefDao, 
         @Qualifier("propertyDefDao")PropertyDefDao propertyDefDao,
         @Qualifier("ocmService")OCMService ocmService,
         @Qualifier("securityService")SecurityService securityService,
         @Qualifier("domainService") DomainService domainService,
         @Qualifier("dtoService") DTOService dtoService) {
      this.storageDao = storageDao;
      this.classDefDao = classDefDao;
      this.ocmService = ocmService;
      this.securityService = securityService;
      this.domainService = domainService;
      this.dtoService = dtoService;
      this.propertyDefDao = propertyDefDao;
   }

   public List<ClassInstanceDto> findClassInstChilds(long parentClassDefId, DTOConfiguration configuration) {
      List<ClassInstanceDto> classIntDtos = new ArrayList<ClassInstanceDto>();
      try {
         List<ClassDefinition> classDefinitions = this.classDefDao
               .findByParentId(parentClassDefId, new ClassDefinitionType[] { ClassDefinitionType.DOCUMENTCLASSINST,
                     ClassDefinitionType.CUSTOMOBJECTINST });
         if (classDefinitions != null && classDefinitions.size() > 0) {
            ClassDefinition parentClass = this.classDefDao.findById(parentClassDefId);
            Storage storage = parentClass.getStorage();
            List<PropertyDefinitionDto> propertyDefDtos = this.dtoService.convertPropertyDtos(parentClass.getAllProperties(), configuration);
            List<ClassInstance> classInsts =  this.ocmService.getClassInstanceProperties(storage, classDefinitions, this.dtoService.getExcludes(configuration));
            for (ClassInstance classInstance : classInsts) {
               Map<PropertyDefinition, Object> propertyValues = classInstance.getProperties();
               ClassInstanceDto classInstanceDto = new ClassInstanceDto();
               classInstanceDto.setClassDefinition(this.dtoService.convertClassDto(classInstance.getClassDefinition(), DTOConfiguration.instance(IDMapping.CLASSDEF_LIMIT)));
               classInstanceDto.setProperties(OCMUtil.convertFromProperyValues(propertyValues, propertyDefDtos));
               classInstanceDto.setHasSyncSecurity(classInstance.isHasSyncSecurity());
               classIntDtos.add(classInstanceDto);
            }
         }
      }
      catch (Exception e) {
         throw new OCMRuntimeException(e);
      }
      return classIntDtos;
   }

   public ClassInstanceDto findClassInst(long classInstId, DTOConfiguration configuration) {
      ClassDefinition classDefinition = this.classDefDao.findById(classInstId);
      this.securityService.verifyPermission(classDefinition.getSecurity(), Permission.VIEWPROPERTIES);
      ClassInstanceDto classInstanceDto = new ClassInstanceDto();
      try {
         List<ClassDefinition> classDefinitions = new ArrayList<ClassDefinition>();
         classDefinitions.add(classDefinition);
         Storage storage = classDefinition.getStorage();
         List<PropertyDefinitionDto> propertyDefDtos = this.dtoService.convertPropertyDtos(classDefinition.getAllProperties(), configuration);
         List<ClassInstance> classInsts =  this.ocmService.getClassInstanceProperties(storage, classDefinitions, this.dtoService.getExcludes(configuration));
         for (ClassInstance classInstance : classInsts) {
            Map<PropertyDefinition, Object> propertyValues = classInstance.getProperties();
            classInstanceDto.setClassDefinition(this.dtoService.convertClassDto(classInstance.getClassDefinition(), DTOConfiguration.instance(IDMapping.CLASSDEF_LIMIT)));
            classInstanceDto.setProperties(OCMUtil.convertFromProperyValues(propertyValues, propertyDefDtos));
            classInstanceDto.setHasSyncSecurity(classInstance.isHasSyncSecurity());
            break;
         }
      }
      catch (Exception e) {
         throw new OCMRuntimeException(e);
      }
      return classInstanceDto;
   }

   @Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Exception.class, noRollbackFor= OCMRuntimeException.class)
   public boolean updateInstance(long classDefId, List<PropertyValueDto> propertiesValue) {
      ClassDefinition classDef = this.classDefDao.findById(classDefId);
      boolean hasLink = false; 
      for (PropertyValueDto propertyValueDto : propertiesValue){
         if (propertyValueDto.getProperty().getPropertyType().intValue() == PropertyType.OBJECT.getPropertyType()){
            hasLink = true;
         }else{
            hasLink = false;
            break;
         }
      }
      if (hasLink)
         this.securityService.verifyPermission(classDef.getSecurity(), Permission.LINK);
      else
         this.securityService.verifyPermission(classDef.getSecurity(), Permission.MODIFYPROPERTIES);
      this.storageHelper.setUserInfo(classDef);
      boolean result = false;
      try {
         result = updateInstance(classDef, OCMUtil.convertFromProperyDefs(propertiesValue, classDef.getAllProperties()));
      }catch (Exception e){
         throw new OCMRuntimeException(e);
      }
      return result;
   }

   @Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Exception.class, noRollbackFor= OCMRuntimeException.class)
   public boolean updateInstance(long storageId, String instancesUUID, Map<String, Object> propertiesValue) {
      ClassDefinition classDef = this.classDefDao.findUUID(storageId, instancesUUID);
      boolean hasLink = false; 
      for (Iterator<Object> it = propertiesValue.values().iterator(); it.hasNext();){
         if (it.next() instanceof ReferenceValue){
            hasLink = true;
         }else{
            hasLink = false;
            break;
         }
      }
      if (hasLink)
         this.securityService.verifyPermission(classDef.getSecurity(), Permission.LINK);
      else
         this.securityService.verifyPermission(classDef.getSecurity(), Permission.MODIFYPROPERTIES);

      this.storageHelper.setUserInfo(classDef);
      boolean result = false;
      try {
         result = updateInstance(classDef, OCMUtil.convertFromProperyDefs(propertiesValue, classDef.getAllProperties()));
      }catch (Exception e){
         throw new OCMRuntimeException(e);
      }
      return result;
   }

   private boolean updateInstance(ClassDefinition classDef, Map<PropertyDefinition, Object> properties) {
      List<ClassDefinition> classDefinitions = new ArrayList<ClassDefinition>();
      classDefinitions.add(classDef);
      List<ClassInstance> oldClassInsts = this.ocmService.getClassInstanceProperties(classDef.getStorage(), classDefinitions, new String[]{});
      try {
         List<PropertyDefinition> allProperties = classDef.getAllProperties();
         properties.putAll(this.storageHelper.getUserInfo(allProperties, classDef));
         ClassInstance classInstance = new ClassInstance();
         classInstance.setClassDefinition(classDef);
         classInstance.setProperties(properties);
         this.ocmService.update(classInstance);
      }catch (Exception e){
         throw new OCMRuntimeException(e);
      }
      try {
         this.classDefDao.save(classDef);
      }
      catch (Exception e1) {
         ClassInstance oldClassInstance = new ClassInstance();
         oldClassInstance.setClassDefinition(classDef);
         oldClassInstance.setProperties(oldClassInsts.get(0).getProperties());
         this.ocmService.update(oldClassInstance);
      }
      return true;
   }

   @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
   public boolean assignSecurity(long classInstId, List<SecurityDto> securityDtos) {
      try {
         ClassDefinition classDefinition = this.classDefDao.findById(classInstId);
         Security security = classDefinition.getSecurity();
         this.securityService.verifyPermission(security, Permission.MODIFYPERMISSON);
         if (security == null){
            security = new Security();
            classDefinition.setSecurity(security);
         }
         for (SecurityDto securityDto : securityDtos){
            security.appendSecurity(securityDto.getUserId(), Security.getSecurity(securityDto));
         }
         ClassInstance classInstance = new ClassInstance();
         classInstance.setClassDefinition(classDefinition);
         this.ocmService.syncSecurity(classDefinition);
         this.classDefDao.save(classDefinition);
         return true;
      }catch (Exception e){
         throw new OCMRuntimeException(e);
      }
   }

   @Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Exception.class, noRollbackFor= OCMRuntimeException.class)
   public boolean updatePermission(long classInstId, long userId, int accessMask) {
      ClassDefinition classDefinition = this.classDefDao.findById(classInstId);
      Security security = classDefinition.getSecurity();
      this.securityService.verifyPermission(security, Permission.MODIFYPERMISSON);
      security.updatedPermission(userId, accessMask);
      ClassInstance classInstance = new ClassInstance();
      classInstance.setClassDefinition(classDefinition);
      this.ocmService.syncSecurity(classDefinition);
      this.classDefDao.save(classDefinition);
      return true;
   }

   @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
   public boolean removeSecurity(long classInstId, long userId) {
      try {
         ClassDefinition classDefinition = this.classDefDao.findById(classInstId);
         Security security = classDefinition.getSecurity();
         this.securityService.verifyPermission(security, Permission.MODIFYPERMISSON);
         security.removePermission(userId);
         ClassInstance classInstance = new ClassInstance();
         classInstance.setClassDefinition(classDefinition);
         this.ocmService.syncSecurity(classDefinition);
         this.classDefDao.save(classDefinition);
      return true;
      }catch (Exception e){
         throw new OCMRuntimeException(e);
      }
   }

   @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
   public boolean restoreCreatorSecurity(long classInstId) {
      try {
         ClassDefinition classDefinition = this.classDefDao.findById(classInstId);
         Security security = classDefinition.getSecurity();
         this.securityService.verifyPermission(security, Permission.MODIFYPERMISSON);
         if (security == null){
            security = new Security();
            security.setClassDefinition(classDefinition);
            classDefinition.setSecurity(security);
         }
         Long creatorId = this.securityService.findUserInfoByUsername(classDefinition.getCreatedUser()).getId();
         if (security.hasContainUser(creatorId)){
            security.updatedPermission(creatorId,AccessMask.FULLPERMISSION.getAccessMask());
         }else{
            security.appendSecurity(creatorId,Security.getDefaultCreatorSecurity(creatorId));
         }
         ClassInstance classInstance = new ClassInstance();
         classInstance.setClassDefinition(classDefinition);
         this.ocmService.syncSecurity(classDefinition);
         this.classDefDao.save(classDefinition);
      return true;
      }catch (Exception e){
         throw new OCMRuntimeException(e);
      }
   }

   public boolean syncSecurity(long classInstId) {
      try {
         ClassDefinition classDefinition = this.classDefDao.findById(classInstId);
         Security security = classDefinition.getSecurity();
         this.securityService.verifyPermission(security, Permission.MODIFYPERMISSON);
         ClassInstance classInstance = new ClassInstance();
         classInstance.setClassDefinition(classDefinition);
         this.ocmService.syncSecurity(classDefinition);
      return true;
      }catch (Exception e){
         throw new OCMRuntimeException(e);
      }
   }

   @Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Exception.class, noRollbackFor= OCMRuntimeException.class)
   public boolean checkin(long classDefId, List<PropertyValueDto> propertiesValue) {
      ClassDefinition classDef = this.classDefDao.findById(classDefId);
      this.securityService.verifyPermission(classDef.getSecurity(), Permission.MODIFYPROPERTIES);
      this.storageHelper.setUserInfo(classDef);
      boolean result = false;
      try {
         result = checkin(classDef, OCMUtil.convertFromProperyDefs(propertiesValue, classDef.getAllProperties()));
      }catch (Exception e){
         throw new OCMRuntimeException(e);
      }
      return result;
   }

   @Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Exception.class, noRollbackFor= OCMRuntimeException.class)
   public boolean checkin(long storageId, String guid, Map<String, Object> propertiesValue) {
      ClassDefinition classDefinition = this.classDefDao.findUUID(storageId, guid);
      this.securityService.verifyPermission(classDefinition.getSecurity(), Permission.MODIFYPROPERTIES);
      this.storageHelper.setUserInfo(classDefinition);
      boolean result = false;
      try {
         result = checkin(classDefinition, OCMUtil.convertFromProperyDefs(propertiesValue, classDefinition.getAllProperties()));
      }catch (Exception e){
         throw new OCMRuntimeException(e);
      }
      return result;
   }

   private boolean checkin(ClassDefinition classDef, Map<PropertyDefinition, Object> properties) {
      List<ClassDefinition> classDefinitions = new ArrayList<ClassDefinition>();
      classDefinitions.add(classDef);
      List<PropertyDefinition> allProperties = classDef.getAllProperties();
      try {
         properties.putAll(this.storageHelper.getUserInfo(allProperties, classDef));
         ClassInstance classInstance = new ClassInstance();
         classInstance.setClassDefinition(classDef);
         classInstance.setProperties(properties);
         this.ocmService.checkin(classInstance);
      }catch (Exception e){
         throw new OCMRuntimeException(e);
      }
      try {
         this.classDefDao.save(classDef);
      }
      catch (Exception e1) {
         this.ocmService.removeLastVersion(classDef);
      }
      return true;
   }

   @Transactional(readOnly = false, propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
   public void deleteClassInstance(long storageId, String guid) {
      ClassDefinition classDefinition = this.classDefDao.findUUID(storageId, guid);
      this.securityService.verifyPermission(classDefinition.getSecurity(), Permission.DELETE);
      this.classDefDao.remove(classDefinition.getId());
      this.ocmService.removeClassDefintion(classDefinition);
   }

   @Transactional(readOnly = false, propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
   public void deleteClassInstance(long classInstId) {
      ClassDefinition classDefinition = this.classDefDao.findById(classInstId);
      this.securityService.verifyPermission(classDefinition.getSecurity(), Permission.DELETE);
      this.classDefDao.remove(classDefinition.getId());
      this.ocmService.removeClassDefintion(classDefinition);
   }

   public boolean checkout(long classInstId) {
      ClassDefinition classDefinition = this.classDefDao.findById(classInstId);
      this.securityService.verifyPermission(classDefinition.getSecurity(), Permission.MODIFYPROPERTIES);
      boolean result = this.ocmService.checkout(classDefinition);
      return result;
   }

   public boolean checkout(long storageId, String guid) {
      ClassDefinition classDefinition = this.classDefDao.findUUID(storageId, guid);
      this.securityService.verifyPermission(classDefinition.getSecurity(), Permission.MODIFYPROPERTIES);
      boolean result = this.ocmService.checkout(classDefinition);
      return result;
   }

   public boolean cancelCheckout(long classInstId) {
      ClassDefinition classDefinition = this.classDefDao.findById(classInstId);
      boolean result = this.ocmService.cancelCheckout(classDefinition);
      return result;
   }

   public boolean cancelCheckout(long storageId, String guid) {
      ClassDefinition classDefinition = this.classDefDao.findUUID(storageId, guid);
      boolean result = this.ocmService.cancelCheckout(classDefinition);
      return result;
   }

   public boolean hasExist(long storageId, String guid) {
      ClassDefinition classDefinition = this.classDefDao.findUUID(storageId, guid);
      if (classDefinition != null){
         return true;
      }
      return false;
   }
   public Files downloadFile(long classInstId) {
      ClassDefinition classDefinition = this.classDefDao.findById(classInstId);
      Files files = this.ocmService.download(classDefinition);
      return files;
   }

   public Files downloadFile(String guid, String storageName, String[] propertyDefNames) {
      Storage storage = storageDao.findName(storageName);
      List<PropertyDefinition> properties = this.propertyDefDao.findNames(propertyDefNames, storage.getId());
      Files files = this.ocmService.download(storage, guid, properties);
      return files;
   }

   public List<ClassInstanceDto> getHistory(long classInstId) {
      List<ClassInstanceDto> histories = new ArrayList<ClassInstanceDto>();
      ClassDefinition classDefinition = this.classDefDao.findById(classInstId);
      this.securityService.verifyPermission(classDefinition.getSecurity(), Permission.VIEWPROPERTIES);
      List<ClassInstance> classInsts =  this.ocmService.getHistory(classDefinition);
      for (ClassInstance classInstance : classInsts) {
         Map<PropertyDefinition, Object> propertyValues = classInstance.getProperties();
         List<PropertyValueDto> propertyValueDtos = new ArrayList<PropertyValueDto>();
         for (Iterator<PropertyDefinition> it = propertyValues.keySet().iterator(); it.hasNext();){
            PropertyDefinition propertyDefinition = it.next();
            Object value = propertyValues.get(propertyDefinition);
            PropertyValueDto propertyValueDto = new PropertyValueDto();
            propertyValueDto.setProperty(this.dtoService.convertPropertyDto(propertyDefinition, DTOConfiguration.instance(IDMapping.PROPERTYDEF_DEFAULT)));
            propertyValueDto.setValue(value);
            propertyValueDtos.add(propertyValueDto);
         }
         ClassInstanceDto classInstanceDto = new ClassInstanceDto();
         classInstanceDto.setClassDefinition(this.dtoService.convertClassDto(classInstance.getClassDefinition(), DTOConfiguration.instance(IDMapping.CLASSDEF_LIMIT)));
         classInstanceDto.setProperties(propertyValueDtos);
         histories.add(classInstanceDto);
      }
      return histories;
   }

   public Object getPropertyValue(String classInstGUID, String propertyName, String[] filterPropertyNames, int maxItems, StorageDto storageDto) {
      Storage storage = this.storageDao.findById(storageDto.getId());
      String[] filterProperties = filterPropertyNames;
      if (filterProperties == null){
         filterProperties = new String[1];
         filterProperties[0] = propertyName;
      }else {
         int oldLength = filterPropertyNames.length;
         filterProperties = Arrays.copyOf(filterPropertyNames, oldLength + 1);
         filterProperties[oldLength] = propertyName;
      }
      List<PropertyDefinition> expectedProperties = this.propertyDefDao.findNames(filterProperties, storageDto.getId());
      PropertyDefinition propertyDefinition = OCMUtil.getPropertyDefinition(propertyName, expectedProperties);
      expectedProperties.remove(propertyDefinition);
      Object result = this.ocmService.getPropertyValue(storage, classInstGUID, propertyDefinition, expectedProperties, maxItems);
      return result;
   }

   @Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Exception.class, noRollbackFor= OCMRuntimeException.class)
   public void createInstance(long classDefId, List<PropertyValueDto> propertiesValue){
      ClassDefinition classDef = this.classDefDao.findById(classDefId);
      this.securityService.verifyPermission(classDef.getSecurity(), Permission.CREATEINSTANCE);
      createInstance(classDef, propertiesValue);
   }

   @Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Exception.class, noRollbackFor= OCMRuntimeException.class)
   public ClassInstance createInstance(long storageId, String className, Map<String, Object> propertiesValue, List<SecurityDto> securityDtos) {
      ClassDefinition classDef = this.classDefDao.findName(storageId, className);
      this.securityService.verifyPermission(classDef.getSecurity(), Permission.CREATEINSTANCE);
      ClassDefinition newClassDef = classDef.createClassDefInstance();

      Security security = domainService.convertSecurity(securityDtos);
      security.setClassDefinition(newClassDef);
      newClassDef.setSecurity(security);

      this.storageHelper.setUserInfo(newClassDef);
      ClassInstance classInstance = new ClassInstance();
      if (propertiesValue != null) {
         try {
            List<PropertyDefinition> allProperties = classDef.getAllProperties();
            Map<PropertyDefinition, Object> properties = OCMUtil.convertFromProperyDefs(propertiesValue, allProperties);
            properties.putAll(this.storageHelper.getUserInfo(allProperties, newClassDef));
            classInstance.setClassDefinition(newClassDef);
            classInstance.setProperties(properties);
            classInstance = this.ocmService.createClassInstance(classInstance);
            newClassDef.setClassDefUID(classInstance.getNodeUUID());
            newClassDef.setName(classInstance.getNodeName());
            newClassDef.setDisplayName(classInstance.getNodeName());
            newClassDef.setRealPath(classInstance.getRealPath());
         }
         catch (Exception e) {
            throw new OCMRuntimeException(e);
         }
      }
      try {
         this.classDefDao.save(newClassDef);
      }
      catch (Exception e) {
         this.ocmService.removeClassDefintion(newClassDef);
      }
      return classInstance;
   }

   private ClassInstance createInstance(ClassDefinition classDef, List<PropertyValueDto> propertiesValue) {
      ClassDefinition newClassDef = classDef.createClassDefInstance();
      this.storageHelper.setUserInfo(newClassDef);
      ClassInstance classInstance = new ClassInstance();
      if (propertiesValue != null) {
         try {
            List<PropertyDefinition> allProperties = classDef.getAllProperties();
            Map<PropertyDefinition, Object> properties = OCMUtil.convertFromProperyDefs(propertiesValue, allProperties);
            properties.putAll(this.storageHelper.getUserInfo(allProperties, newClassDef));
            classInstance.setClassDefinition(newClassDef);
            classInstance.setProperties(properties);
            classInstance = this.ocmService.createClassInstance(classInstance);
            newClassDef.setClassDefUID(classInstance.getNodeUUID());
            newClassDef.setName(classInstance.getNodeName());
            newClassDef.setDisplayName(classInstance.getNodeName());
            newClassDef.setRealPath(classInstance.getRealPath());
         }
         catch (Exception e) {
            throw new OCMRuntimeException(e);
         }
      }
      try {
         this.classDefDao.save(newClassDef);
      }
      catch (Exception e) {
         this.ocmService.removeClassDefintion(newClassDef);
      }
      return classInstance;
   }
}
